package ltd.dujiabao.dualwrite.datasource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StreamUtils;

import java.io.*;
import java.math.BigDecimal;
import java.net.URL;
import java.sql.*;
import java.util.Calendar;

/**
 * @author Bowie Du
 * @date 2025/11/1 10:28
 */

public class DualWritePreparedStatement extends DualWriteStatement implements PreparedStatement {
    private static final Logger logger = LoggerFactory.getLogger(DualWritePreparedStatement.class);
    private boolean isAutoGeneratedKeys = false;

    public DualWritePreparedStatement(PreparedStatement priorityStatement,
                                      PreparedStatement secondaryStatement,
                                      DualWriteConnection dualWriteConnection) {
        super(priorityStatement, secondaryStatement, dualWriteConnection);
    }

    public DualWritePreparedStatement(Statement priorityStatement,
                                      Statement secondaryStatement,
                                      DualWriteConnection dualWriteConnection,
                                      boolean isAutoGeneratedKeys) {
        super(priorityStatement, secondaryStatement, dualWriteConnection);
        this.isAutoGeneratedKeys = isAutoGeneratedKeys;
    }

    @Override
    public ResultSet executeQuery() throws SQLException {
        ResultSet resultSet = ((PreparedStatement) priorityStatement).executeQuery();
        try {
            ((PreparedStatement) secondaryStatement).executeQuery();
        } catch (SQLException e) {
            logger.warn("secondaryStatement executeQuery fail.", e);
        }
        return resultSet;
    }

    @Override
    public int executeUpdate() throws SQLException {
        int result = ((PreparedStatement) priorityStatement).executeUpdate();
        if (!isAutoGeneratedKeys) {
            try {
                ((PreparedStatement) secondaryStatement).executeUpdate();
            } catch (SQLException e) {
                logger.warn("secondaryStatement executeUpdate fail.", e);
            }
            return result;
        }
        // 取出id
        ResultSet generatedKeys = priorityStatement.getGeneratedKeys();
        if (!generatedKeys.next()) {
            logger.error("fail to obtain id after insert.");
            return result;
        }
        long id = generatedKeys.getLong(1);
        // 次数据源的插入sql 指定id值
        try {
            int parameterCount = ((PreparedStatement) secondaryStatement).getParameterMetaData().getParameterCount();
            ((PreparedStatement) secondaryStatement).setLong(parameterCount, id);
            ((PreparedStatement) secondaryStatement).executeUpdate();
        } catch (SQLException e) {
            logger.warn("secondaryStatement executeUpdate fail.", e);
        }
        return result;
    }

    @Override
    public void setNull(int parameterIndex, int sqlType) throws SQLException {
        ((PreparedStatement) priorityStatement).setNull(parameterIndex, sqlType);
        ((PreparedStatement) secondaryStatement).setNull(parameterIndex, sqlType);
    }

    @Override
    public void setBoolean(int parameterIndex, boolean x) throws SQLException {
        ((PreparedStatement) priorityStatement).setBoolean(parameterIndex, x);
        ((PreparedStatement) secondaryStatement).setBoolean(parameterIndex, x);
    }

    @Override
    public void setByte(int parameterIndex, byte x) throws SQLException {
        ((PreparedStatement) priorityStatement).setByte(parameterIndex, x);
        ((PreparedStatement) secondaryStatement).setByte(parameterIndex, x);
    }

    @Override
    public void setShort(int parameterIndex, short x) throws SQLException {
        ((PreparedStatement) priorityStatement).setShort(parameterIndex, x);
        ((PreparedStatement) secondaryStatement).setShort(parameterIndex, x);
    }

    @Override
    public void setInt(int parameterIndex, int x) throws SQLException {
        ((PreparedStatement) priorityStatement).setInt(parameterIndex, x);
        ((PreparedStatement) secondaryStatement).setInt(parameterIndex, x);
    }

    @Override
    public void setLong(int parameterIndex, long x) throws SQLException {
        ((PreparedStatement) priorityStatement).setLong(parameterIndex, x);
        ((PreparedStatement) secondaryStatement).setLong(parameterIndex, x);
    }

    @Override
    public void setFloat(int parameterIndex, float x) throws SQLException {
        ((PreparedStatement) priorityStatement).setFloat(parameterIndex, x);
        ((PreparedStatement) secondaryStatement).setFloat(parameterIndex, x);
    }

    @Override
    public void setDouble(int parameterIndex, double x) throws SQLException {
        ((PreparedStatement) priorityStatement).setDouble(parameterIndex, x);
        ((PreparedStatement) secondaryStatement).setDouble(parameterIndex, x);
    }

    @Override
    public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException {
        ((PreparedStatement) priorityStatement).setBigDecimal(parameterIndex, x);
        ((PreparedStatement) secondaryStatement).setBigDecimal(parameterIndex, x);
    }

    @Override
    public void setString(int parameterIndex, String x) throws SQLException {
        ((PreparedStatement) priorityStatement).setString(parameterIndex, x);
        ((PreparedStatement) secondaryStatement).setString(parameterIndex, x);
    }

    @Override
    public void setBytes(int parameterIndex, byte[] x) throws SQLException {
        ((PreparedStatement) priorityStatement).setBytes(parameterIndex, x);
        ((PreparedStatement) secondaryStatement).setBytes(parameterIndex, x);
    }

    @Override
    public void setDate(int parameterIndex, Date x) throws SQLException {
        ((PreparedStatement) priorityStatement).setDate(parameterIndex, x);
        ((PreparedStatement) secondaryStatement).setDate(parameterIndex, x);
    }

    @Override
    public void setTime(int parameterIndex, Time x) throws SQLException {
        ((PreparedStatement) priorityStatement).setTime(parameterIndex, x);
        ((PreparedStatement) secondaryStatement).setTime(parameterIndex, x);
    }

    @Override
    public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException {
        ((PreparedStatement) priorityStatement).setTimestamp(parameterIndex, x);
        ((PreparedStatement) secondaryStatement).setTimestamp(parameterIndex, x);
    }

    @Override
    public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException {
        ((PreparedStatement) priorityStatement).setAsciiStream(parameterIndex, x, length);
        ((PreparedStatement) secondaryStatement).setAsciiStream(parameterIndex, x, length);
    }

    @Override
    public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException {
        ((PreparedStatement) priorityStatement).setUnicodeStream(parameterIndex, x, length);
        ((PreparedStatement) secondaryStatement).setUnicodeStream(parameterIndex, x, length);
    }

    @Override
    public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException {
        ((PreparedStatement) priorityStatement).setBinaryStream(parameterIndex, x, length);
        ((PreparedStatement) secondaryStatement).setBinaryStream(parameterIndex, x, length);
    }

    @Override
    public void clearParameters() throws SQLException {
        ((PreparedStatement) priorityStatement).clearParameters();
        ((PreparedStatement) secondaryStatement).clearParameters();
    }

    @Override
    public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException {
        ((PreparedStatement) priorityStatement).setObject(parameterIndex, x, targetSqlType);
        ((PreparedStatement) secondaryStatement).setObject(parameterIndex, x, targetSqlType);
    }

    @Override
    public void setObject(int parameterIndex, Object x) throws SQLException {
        ((PreparedStatement) priorityStatement).setObject(parameterIndex, x);
        ((PreparedStatement) secondaryStatement).setObject(parameterIndex, x);
    }

    @Override
    public boolean execute() throws SQLException {
        boolean execute = ((PreparedStatement) priorityStatement).execute();
        try {
            ((PreparedStatement) secondaryStatement).execute();
        } catch (SQLException e) {
            logger.warn("secondaryStatement execute fail.", e);
        }
        return execute;
    }

    @Override
    public void addBatch() throws SQLException {
        ((PreparedStatement) priorityStatement).addBatch();
        try {
            ((PreparedStatement) secondaryStatement).addBatch();
        } catch (SQLException e) {
            logger.warn("secondaryStatement addBatch fail.", e);
        }
    }

    @Override
    public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException {
        ((PreparedStatement) priorityStatement).setCharacterStream(parameterIndex, reader, length);
        ((PreparedStatement) secondaryStatement).setCharacterStream(parameterIndex, reader, length);
    }

    @Override
    public void setRef(int parameterIndex, Ref x) throws SQLException {
        ((PreparedStatement) priorityStatement).setRef(parameterIndex, x);
        ((PreparedStatement) secondaryStatement).setRef(parameterIndex, x);
    }

    @Override
    public void setBlob(int parameterIndex, Blob x) throws SQLException {
        ((PreparedStatement) priorityStatement).setBlob(parameterIndex, x);
        ((PreparedStatement) secondaryStatement).setBlob(parameterIndex, x);
    }

    @Override
    public void setClob(int parameterIndex, Clob x) throws SQLException {
        ((PreparedStatement) priorityStatement).setClob(parameterIndex, x);
        ((PreparedStatement) secondaryStatement).setClob(parameterIndex, x);
    }

    @Override
    public void setArray(int parameterIndex, Array x) throws SQLException {
        ((PreparedStatement) priorityStatement).setArray(parameterIndex, x);
        ((PreparedStatement) secondaryStatement).setArray(parameterIndex, x);
    }

    @Override
    public ResultSetMetaData getMetaData() throws SQLException {
        return ((PreparedStatement) priorityStatement).getMetaData();
    }

    @Override
    public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException {
        ((PreparedStatement) priorityStatement).setDate(parameterIndex, x, cal);
        ((PreparedStatement) secondaryStatement).setDate(parameterIndex, x, cal);
    }

    @Override
    public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException {
        ((PreparedStatement) priorityStatement).setTime(parameterIndex, x, cal);
        ((PreparedStatement) secondaryStatement).setTime(parameterIndex, x, cal);
    }

    @Override
    public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException {
        ((PreparedStatement) priorityStatement).setTimestamp(parameterIndex, x, cal);
        ((PreparedStatement) secondaryStatement).setTimestamp(parameterIndex, x, cal);
    }

    @Override
    public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException {
        ((PreparedStatement) priorityStatement).setNull(parameterIndex, sqlType, typeName);
        ((PreparedStatement) secondaryStatement).setNull(parameterIndex, sqlType, typeName);
    }

    @Override
    public void setURL(int parameterIndex, URL x) throws SQLException {
        ((PreparedStatement) priorityStatement).setURL(parameterIndex, x);
        ((PreparedStatement) secondaryStatement).setURL(parameterIndex, x);
    }

    @Override
    public ParameterMetaData getParameterMetaData() throws SQLException {
        return ((PreparedStatement) priorityStatement).getParameterMetaData();
    }

    @Override
    public void setRowId(int parameterIndex, RowId x) throws SQLException {
        ((PreparedStatement) priorityStatement).setRowId(parameterIndex, x);
        ((PreparedStatement) secondaryStatement).setRowId(parameterIndex, x);
    }

    @Override
    public void setNString(int parameterIndex, String value) throws SQLException {
        ((PreparedStatement) priorityStatement).setNString(parameterIndex, value);
        ((PreparedStatement) secondaryStatement).setNString(parameterIndex, value);
    }

    @Override
    public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException {
        ((PreparedStatement) priorityStatement).setNCharacterStream(parameterIndex, value, length);
        ((PreparedStatement) secondaryStatement).setNCharacterStream(parameterIndex, value, length);
    }

    @Override
    public void setNClob(int parameterIndex, NClob value) throws SQLException {
        ((PreparedStatement) priorityStatement).setNClob(parameterIndex, value);
        ((PreparedStatement) secondaryStatement).setNClob(parameterIndex, value);
    }

    @Override
    public void setClob(int parameterIndex, Reader reader, long length) throws SQLException {
        ((PreparedStatement) priorityStatement).setClob(parameterIndex, reader, length);
        ((PreparedStatement) secondaryStatement).setClob(parameterIndex, reader, length);
    }

    @Override
    public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException {
        ((PreparedStatement) priorityStatement).setBlob(parameterIndex, inputStream, length);
        ((PreparedStatement) secondaryStatement).setBlob(parameterIndex, inputStream, length);
    }

    @Override
    public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException {
        ((PreparedStatement) priorityStatement).setNClob(parameterIndex, reader, length);
        ((PreparedStatement) secondaryStatement).setNClob(parameterIndex, reader, length);
    }

    @Override
    public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException {
        ((PreparedStatement) priorityStatement).setSQLXML(parameterIndex, xmlObject);
        ((PreparedStatement) secondaryStatement).setSQLXML(parameterIndex, xmlObject);
    }

    @Override
    public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLException {
        ((PreparedStatement) priorityStatement).setObject(parameterIndex, x, targetSqlType, scaleOrLength);
        ((PreparedStatement) secondaryStatement).setObject(parameterIndex, x, targetSqlType, scaleOrLength);
    }

    @Override
    public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException {
        byte[] bytes;
        try {
            bytes = StreamUtils.copyToByteArray(x);
        } catch (IOException e) {
            logger.error("inputStream error", e);
            throw new SQLException(e);
        }

        try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes)) {
            ((PreparedStatement) priorityStatement).setAsciiStream(parameterIndex, inputStream, length);
        } catch (IOException e) {
            logger.error("priorityStatement setAsciiStream inputStream error", e);
            throw new SQLException(e);
        }

        try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes)) {
            ((PreparedStatement) secondaryStatement).setAsciiStream(parameterIndex, inputStream, length);
        } catch (IOException e) {
            logger.error("secondaryStatement setAsciiStream inputStream error", e);
        }
    }

    @Override
    public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException {
        byte[] bytes;
        try {
            bytes = StreamUtils.copyToByteArray(x);
        } catch (IOException e) {
            logger.error("inputStream error", e);
            throw new SQLException(e);
        }

        try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes)) {
            ((PreparedStatement) priorityStatement).setBinaryStream(parameterIndex, inputStream, length);
        } catch (IOException e) {
            logger.error("priorityStatement setBinaryStream inputStream error", e);
            throw new SQLException(e);
        }

        try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes)) {
            ((PreparedStatement) secondaryStatement).setBinaryStream(parameterIndex, inputStream, length);
        } catch (IOException e) {
            logger.error("secondaryStatement setBinaryStream inputStream error", e);
        }
    }

    @Override
    public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException {
        // 将Reader内容读取到字符串中，以便可以重复使用
        String content;
        try {
            char[] buffer = new char[4096];
            StringBuilder sb = new StringBuilder();
            int charsRead;
            while ((charsRead = reader.read(buffer)) != -1) {
                sb.append(buffer, 0, charsRead);
            }
            content = sb.toString();
        } catch (IOException e) {
            logger.error("Reader error", e);
            throw new SQLException(e);
        }

        try (StringReader stringReader = new StringReader(content)) {
            ((PreparedStatement) priorityStatement).setCharacterStream(parameterIndex, stringReader, length);
        }

        try (StringReader stringReader = new StringReader(content)) {
            ((PreparedStatement) secondaryStatement).setCharacterStream(parameterIndex, stringReader, length);
        }
    }

    @Override
    public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException {
        byte[] bytes;
        try {
            bytes = StreamUtils.copyToByteArray(x);
        } catch (IOException e) {
            logger.error("inputStream error", e);
            throw new SQLException(e);
        }

        try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes)) {
            ((PreparedStatement) priorityStatement).setAsciiStream(parameterIndex, inputStream);
        } catch (IOException e) {
            logger.error("priorityStatement setAsciiStream inputStream error", e);
            throw new SQLException(e);
        }

        try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes)) {
            ((PreparedStatement) secondaryStatement).setAsciiStream(parameterIndex, inputStream);
        } catch (IOException e) {
            logger.error("secondaryStatement setAsciiStream inputStream error", e);
        }
    }

    @Override
    public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException {
        byte[] bytes;
        try {
            bytes = StreamUtils.copyToByteArray(x);
        } catch (IOException e) {
            logger.error("inputStream error", e);
            throw new SQLException(e);
        }

        try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes)) {
            ((PreparedStatement) priorityStatement).setBinaryStream(parameterIndex, inputStream);
        } catch (IOException e) {
            logger.error("priorityStatement set inputStream error", e);
            throw new SQLException(e);
        }

        try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes)) {
            ((PreparedStatement) secondaryStatement).setBinaryStream(parameterIndex, inputStream);
        } catch (IOException e) {
            logger.error("secondaryStatement set inputStream error", e);
        }
    }

    @Override
    public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException {
        // 将Reader内容读取到字符串中，以便可以重复使用
        String content;
        try {
            char[] buffer = new char[4096];
            StringBuilder sb = new StringBuilder();
            int charsRead;
            while ((charsRead = reader.read(buffer)) != -1) {
                sb.append(buffer, 0, charsRead);
            }
            content = sb.toString();
        } catch (IOException e) {
            logger.error("Reader error", e);
            throw new SQLException(e);
        }

        try (StringReader stringReader = new StringReader(content)) {
            ((PreparedStatement) priorityStatement).setCharacterStream(parameterIndex, stringReader);
        }

        try (StringReader stringReader = new StringReader(content)) {
            ((PreparedStatement) secondaryStatement).setCharacterStream(parameterIndex, stringReader);
        }
    }

    @Override
    public void setNCharacterStream(int parameterIndex, Reader reader) throws SQLException {
        // 将Reader内容读取到字符串中，以便可以重复使用
        String content;
        try {
            char[] buffer = new char[4096];
            StringBuilder sb = new StringBuilder();
            int charsRead;
            while ((charsRead = reader.read(buffer)) != -1) {
                sb.append(buffer, 0, charsRead);
            }
            content = sb.toString();
        } catch (IOException e) {
            logger.error("Reader error", e);
            throw new SQLException(e);
        }

        try (StringReader stringReader = new StringReader(content)) {
            ((PreparedStatement) priorityStatement).setNCharacterStream(parameterIndex, stringReader);
        }

        try (StringReader stringReader = new StringReader(content)) {
            ((PreparedStatement) secondaryStatement).setNCharacterStream(parameterIndex, stringReader);
        }
    }

    @Override
    public void setClob(int parameterIndex, Reader reader) throws SQLException {
        // 将Reader内容读取到字符串中，以便可以重复使用
        String content;
        try {
            char[] buffer = new char[4096];
            StringBuilder sb = new StringBuilder();
            int charsRead;
            while ((charsRead = reader.read(buffer)) != -1) {
                sb.append(buffer, 0, charsRead);
            }
            content = sb.toString();
        } catch (IOException e) {
            logger.error("Reader error", e);
            throw new SQLException(e);
        }

        try (StringReader stringReader = new StringReader(content)) {
            ((PreparedStatement) priorityStatement).setClob(parameterIndex, stringReader);
        }

        try (StringReader stringReader = new StringReader(content)) {
            ((PreparedStatement) secondaryStatement).setClob(parameterIndex, stringReader);
        }
    }

    @Override
    public void setBlob(int parameterIndex, InputStream x) throws SQLException {
        byte[] bytes;
        try {
            bytes = StreamUtils.copyToByteArray(x);
        } catch (IOException e) {
            logger.error("inputStream error", e);
            throw new SQLException(e);
        }

        try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes)) {
            ((PreparedStatement) priorityStatement).setBlob(parameterIndex, inputStream);
        } catch (IOException e) {
            logger.error("priorityStatement set inputStream error", e);
            throw new SQLException(e);
        }

        try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes)) {
            ((PreparedStatement) secondaryStatement).setBlob(parameterIndex, inputStream);
        } catch (IOException e) {
            logger.error("secondaryStatement set inputStream error", e);
        }
    }

    @Override
    public void setNClob(int parameterIndex, Reader reader) throws SQLException {
        // 将Reader内容读取到字符串中，以便可以重复使用
        String content;
        try {
            char[] buffer = new char[4096];
            StringBuilder sb = new StringBuilder();
            int charsRead;
            while ((charsRead = reader.read(buffer)) != -1) {
                sb.append(buffer, 0, charsRead);
            }
            content = sb.toString();
        } catch (IOException e) {
            logger.error("Reader error", e);
            throw new SQLException(e);
        }

        try (StringReader stringReader = new StringReader(content)) {
            ((PreparedStatement) priorityStatement).setNClob(parameterIndex, stringReader);
        }

        try (StringReader stringReader = new StringReader(content)) {
            ((PreparedStatement) secondaryStatement).setNClob(parameterIndex, stringReader);
        }
    }
}
